import { ArrUtil } from "../../util/ArrUtil";
import { BTNode } from "../BTNode";
import { BehaviorTree } from "../BehaviorTree";
import { BTConditional } from "../decorator/BTConditional";
import { BTState } from "../BTState";
import { isIConditional } from "../IConditional";
import { BTAbortType, hasAbortType } from "./BTAbortType";

/**
 * [行为树]复合节点
 */
export abstract class BTComposite<T = any> extends BTNode<T> {

    /** 当前执行到第几个子节点 */
    curIdx: number = 0;
    protected _hasLowerPriorityConditionalAbort: boolean = false;

    constructor(
        /** 子节点 */
        public children: BTNode<T>[],
        /** 中断类型 */
        public abortType = BTAbortType.none) {
        super();
    }

    setTree(tree: BehaviorTree<T>): void {
        super.setTree(tree);
        this.children.forEach(node => node.setTree(tree));
    }

    done(): void {
        super.done();
        this.children.forEach(child => child.done());
    }

    protected onStart(ctx: T): void {
        // 较低优先级的中止发生在下一级，所以我们在这里检查是否有
        this._hasLowerPriorityConditionalAbort = this.hasLowerPriorityConditionalAbortInChildren();
        this.curIdx = 0;
    }

    protected onStop(ctx: T): void {
        super.done();
        this.children.forEach(child => child.done());
    }

    /**
     * 添加子节点
     * @param node 
     */
    addChild(node: BTNode<T>) {
        this.children.push(node);
    }

    /**
     * 移除子节点
     * @param node 
     */
    removeChild(node: BTNode<T>) {
        ArrUtil.remove(this.children, node);
    }

    /**
     * 检查复合体的子代，看是否有具有LowerPriority AbortType的复合体
     */
    private hasLowerPriorityConditionalAbortInChildren(): boolean {
        for (let i = 0; i < this.children.length; i++) {
            // 检查是否有一个设置了中止类型的复合体
            let composite = this.children[i] as BTComposite<T>;
            if (composite != null && hasAbortType(composite.abortType, BTAbortType.lowerPriority)) {
                // 现在确保第一个子节点是一个条件性的
                if (composite.isFirstChildConditional())
                    return true;
            }
        }

        return false;
    }

    /**
     * 如果一个复合体的第一个子节点是一个条件体，返回true。用来处理条件性中止
     */
    public isFirstChildConditional(): boolean {
        return isIConditional(this.children[0]);
    }

    /**
     * 检查任何IConditional的子代，看它们是否已经改变了状态
     */
    protected updateSelfAbortConditional(context: T, statusCheck: BTState) {
        // 检查任何IConditional的子任务，看它们是否改变了状态
        for (let i = 0; i < this.curIdx; i++) {

            let child = this.children[i];
            if (!isIConditional(child))
                continue;

            let status = this.updateConditionalNode(context, child);
            if (status != statusCheck) {
                this.curIdx = i;

                // 我们有一个中止，所以我们使子节点无效，所以他们被重新评估
                for (let j = i; j < this.children.length; j++)
                    this.children[j].done();
                break;
            }
        }
    }

    /**
     * 检查任何具有LowerPriority AbortType和Conditional作为第一个子代的组合体。
     * 如果它找到一个，它将执行条件，如果状态不等于 statusCheck，curIdx将被更新，即当前运行的Action将被中止。
     */
    protected updateLowerPriorityAbortConditional(context: T, statusCheck: BTState) {
        // 检查任何较低优先级的任务，看它们是否改变了状态
        for (let i = 0; i < this.curIdx; i++) {
            let composite = this.children[i] as BTComposite<T>;
            if (composite != null && hasAbortType(composite.abortType, BTAbortType.lowerPriority)) {
                // 现在我们只得到条件的状态（更新而不是执行），看看它是否发生了变化，并对条件装饰器加以注意
                let child = composite.children[0];
                let status = this.updateConditionalNode(context, child);
                if (status != statusCheck) {
                    this.curIdx = i;

                    // 我们有一个中止，所以我们使子节点无效，所以他们被重新评估
                    for (let j = i; j < this.children.length; j++)
                        this.children[j].done();

                    break;
                }
            }
        }
    }

    /**
     * 帮助器，用于获取一个条件或一个条件装饰器的任务状态
     * @param ctx 
     * @param node 
     * @returns 
     */
    private updateConditionalNode(ctx: T, node: BTNode<T>) {
        if (node instanceof BTConditional)
            return (node as BTConditional<T>).executeConditional(ctx, true);
        else
            return node.process(ctx);
    }

}